package com.vains.config.security;

import com.vains.bean.Result;
import com.vains.config.properties.RemoteTokenServicesProperties;
import com.vains.config.properties.ResourceProperties;
import com.vains.constant.ErrorCodeConstants;
import com.vains.util.ServletUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 资源服务器配置
 *
 * @author vains
 * @date 2021/4/4 21:39
 */
@Slf4j
@Configuration
@AllArgsConstructor
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    private final ResourceProperties resourceProperties;

    private final RemoteTokenServicesProperties remoteTokenServicesProperties;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        // 配置当前资源服务器的ID，默认使用authorizeResource
        resources.resourceId(resourceProperties.getResourceId());
        // 实现令牌服务，从配置文件中获取
        resources.tokenServices(tokenServices());
        // 资源服务器认证失败时调用(检查token出现错误时)
        resources.authenticationEntryPoint(this::authenticationEntryPoint);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        //不创建session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        // 所有请求都需要认证; 通过注解 EnableGlobalMethodSecurity 实现方法级别鉴权
        http.authorizeRequests()
            .antMatchers(resourceProperties.getWhiteList().toArray(new String[]{})).permitAll()
            .and().authorizeRequests()
            .anyRequest().authenticated();
    }

    /**
     * 资源服务器认证失败时调用
     * @param request 请求
     * @param response 响应
     * @param e 具体的异常
     */
    private void authenticationEntryPoint(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
        log.error("token异常.");
        String errorPrev = "Token异常：";
        String message;
        if (e.getMessage().startsWith(errorPrev)) {
            message = e.getMessage();
        } else {
            message = "Token异常: " + e.getMessage();
        }
        OAuth2Exception exception = OAuth2Exception.create(OAuth2Exception.INVALID_TOKEN, e.getMessage());
        Result<OAuth2Exception> oAuth2ExceptionResult = Result.oauth2Error(ErrorCodeConstants.INVALID_REQUEST_TOKEN, message, exception);
        ServletUtils.writeDataByResponse(response, oAuth2ExceptionResult);
    }

    /**
     * 注入检查token的服务
     * @return CustomRemoteTokenServices 自定义的远程检查token服务
     */
    @Bean
    public RemoteTokenServices tokenServices() {
        RemoteTokenServices tokenServices = new CustomRemoteTokenServices();
        RemoteTokenServicesProperties.Resource resource = remoteTokenServicesProperties.getResource();
        tokenServices.setCheckTokenEndpointUrl(resource.getTokenInfoUri());
        RemoteTokenServicesProperties.Client client = remoteTokenServicesProperties.getClient();
        tokenServices.setClientId(client.getClientId());
        tokenServices.setClientSecret(client.getClientSecret());
        return tokenServices;
    }

}
